package com.maybach.workflow.core.config.parser;

import com.maybach.workflow.common.api.FlowChain;
import com.maybach.workflow.common.api.enums.FlowTypeEnum;
import com.maybach.workflow.common.api.enums.NodeTypeEnum;
import com.maybach.workflow.common.api.node.BaseNode;
import com.maybach.workflow.common.api.node.ForkNode;
import com.maybach.workflow.core.helper.StreamHelper;
import com.maybach.workflow.core.helper.XmlHelper;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by well on 15/6/9.
 */
public class NodeParser {

    public static final String ATTR_NAME = "name";
    public static final String ATTR_ORDER = "order";
    public static final String ATTR_DISPLAYNAME = "displayName";
    public static final String ATTR_ACTOR_TYPE = "type";
    public static final String ATTR_ACTOR_VALUE = "value";
    public static final String ATTR_TO = "to";
    public static final String ATTR_HANDLERS = "handlers";
    public static final String ATTR_BEFORE_HANDLERS = "beforeHandlers";
    public static final String ATTR_AFTER_HANDLERS = "afterHandlers";
    public static final String ATTR_EXECUTE_BEAN_NAME = "executeBeanName";
    public static final String ATTR_TASK_TYPE = "taskType";
    public static final String ATTR_URL = "url";
    public static final String ATTR_PARENT = "parent";

    public static final String ATTR_EXPRESSION = "expression";
    public static final String ATTR_VERSION = "version";
    public static final String NODE_NAME_TASK = NodeTypeEnum.task.name();
    public static final String NODE_NAME_JOIN = NodeTypeEnum.join.name();
    public static final String NODE_NAME_FORK = NodeTypeEnum.fork.name();
    public static final String NODE_NAME_END = NodeTypeEnum.end.name();
    public static final String NODE_NAME_START = NodeTypeEnum.start.name();

    public static final String NODE_NAME_DECISION = NodeTypeEnum.decision.name();

    public static final String NODE_NAME_TRANSACTION = NodeTypeEnum.transaction.name();
    public static final String NODE_NAME_ACTOR = NodeTypeEnum.actor.name();



    private static Map<String, Parser> parserMap = new HashMap<String, Parser>();

    public NodeParser() {
        parserMap.put(NODE_NAME_TASK, new TaskParser());
        parserMap.put(NODE_NAME_JOIN, new JoinParser());
        parserMap.put(NODE_NAME_FORK, new ForkParser());
        parserMap.put(NODE_NAME_END, new EndParser());
        parserMap.put(NODE_NAME_START, new StartParser());

        parserMap.put(NODE_NAME_DECISION, new DecisionParser());
        parserMap.put(NODE_NAME_TRANSACTION, new TransactionParser());
        parserMap.put(NODE_NAME_ACTOR, new TaskActorParser());
    }

    public FlowChain parser(String resource) {
        Map<String,BaseNode> map = new HashMap<String, BaseNode>();
        FlowChain flowChain =  new FlowChain();
        flowChain.setMap(map);
        DocumentBuilder documentBuilder = XmlHelper.createDocumentBuilder();
        try {
            if (documentBuilder != null) {
                InputStream input = StreamHelper.openStream(resource);
                if (input == null){  throw new RuntimeException("can not find resource:"+resource);}
                Document doc = documentBuilder.parse(input);
                Element configElement = doc.getDocumentElement();
                if(configElement.getNodeName().equals(FlowTypeEnum.fragment.name())){
                    flowChain.setFlowTypeEnum(FlowTypeEnum.fragment);
                }else{
                    flowChain.setFlowTypeEnum(FlowTypeEnum.flow);
                }
                String flowName = configElement.getAttribute(ATTR_NAME);
                flowChain.setName(flowName);
                String order = configElement.getAttribute(ATTR_ORDER);
                if(StringUtils.hasText(order)){
                    flowChain.setOrder(Integer.valueOf(order));
                }
                String flowDetailUrl =  configElement.getAttribute(ATTR_URL);
                if(StringUtils.hasText(flowDetailUrl)){
                    flowChain.setUrl(flowDetailUrl);
                }
                flowChain.setDisplayName(configElement.getAttribute(ATTR_DISPLAYNAME));
                flowChain.setExpression(configElement.getAttribute(ATTR_EXPRESSION));
                String version = configElement.getAttribute(ATTR_VERSION);
                if(StringUtils.hasText(version)) {
                    flowChain.setVersion(Integer.parseInt(version));
                }else{
                    flowChain.setVersion(0);
                }
                NodeList nodeList = configElement.getChildNodes();
                int nodeSize = nodeList.getLength();
                for (int i = 0; i < nodeSize; i++) {
                    Node node = nodeList.item(i);
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        BaseNode parse = parserMap.get(node.getNodeName()).parse((Element) node);
                        if(parse.getNodeType() == NodeTypeEnum.task) {
                            if(org.apache.commons.lang.StringUtils.isBlank(parse.getName())){
                                throw new RuntimeException("action is Blank:" );
                            }
                            /*NewOrderActionEnum newOrderActionEnum = NewOrderActionEnum.valueOf(parse.getName());
                            if (newOrderActionEnum == null) {
                                throw new RuntimeException("action not define:" + parse.getName());
                            }*/
                        }
                        if(map.containsKey(parse.getName())){
                            throw new RuntimeException("node name duplicate:name:"+parse.getName()+", resource="+resource);
                        }
                        map.put(parse.getName(),parse);
                        fillChild(map,parse);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return flowChain;
    }

    private void fillChild( Map<String,BaseNode> map,BaseNode baseNode){
        if(baseNode.getNodeType() == NodeTypeEnum.fork){
            ForkNode forkNode = (ForkNode)baseNode;
            for(BaseNode base:forkNode.getForkNodeList()){
                map.put(base.getName(),base);
                fillChild(map,base);
            }
        }
    }

    public static Map<String, Parser> getParserMap() {
        return parserMap;
    }

    public static void main(String[] args) {
        NodeParser nodeParser = new NodeParser();
        nodeParser.parser("flow/workflow-orderloan.xml");
    }
}
